package com.acheng.ws.client;

import com.acheng.ws.client.handler.WebSocketClientHandler;
import com.acheng.ws.client.listener.WebSocketListener;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public abstract class AbstractClient {

    private WebSocketListener listener;

    private static ExecutorService threadExecutor = Executors.newSingleThreadExecutor();

    private int port = 80;

    private URI webSocketURL;

    private HttpHeaders headers = new DefaultHttpHeaders();

    private EventLoopGroup group = new NioEventLoopGroup();

    private WebSocketClientHandler handler;

    public AbstractClient(String url, WebSocketListener listener) throws URISyntaxException {
        this.webSocketURL = new URI(url);
        this.port = webSocketURL.getPort() == -1 ? 80 : webSocketURL.getPort();
        this.listener = listener;
    }

    public void addHeader(String name, Object value) {
        headers.add(name, value);
    }

    public void init() throws InterruptedException {
        handler = new WebSocketClientHandler(
                WebSocketClientHandshakerFactory.newHandshaker(
                        webSocketURL,
                        WebSocketVersion.V13,
                        null,
                        true,
                        headers,
                        Integer.MAX_VALUE),
                this.listener
        );
        threadExecutor.submit(new Runnable() {
            @Override
            public void run() {
                Bootstrap b = new Bootstrap();
                b.group(group)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) {
                                ChannelPipeline p = ch.pipeline();
                                p.addLast(
                                        new HttpClientCodec(),
                                        new HttpObjectAggregator(65536),
                                        WebSocketClientCompressionHandler.INSTANCE,
                                        handler);
                            }
                        });

                Channel ch = null;
                try {
                    ch = b.connect(webSocketURL.getHost(), port).sync().channel();
                    handler.handshakeFuture().sync();
                    ch.closeFuture().sync();
                } catch (InterruptedException e) {
                    listener.onFailure(null, e);
                }
            }
        });

    }

    public void shutdown() {
        group.shutdownGracefully();
    }

    public boolean isConnection() {
        return this.handler.isConnection();
    }

    public void send(String text) {
        this.handler.send(text);
    }

    public void send(byte[] bytes) {
        this.handler.send(bytes);
    }

    public long ping() {
        return this.handler.ping();
    }
}
